package hotel.quartz.oversea.job.meituan;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import hotel.base.oversea.constant.ApiConstans;
import hotel.base.oversea.constant.CommonConstants;
import hotel.base.oversea.constant.CommonUrlConstants;
import hotel.base.oversea.constant.MeituanConstants;
import hotel.base.oversea.constant.quartz.UrlMtEnums;
import hotel.base.oversea.utils.Result;
import hotel.quartz.oversea.config.service.RedisService;
import hotel.quartz.oversea.entity.form.JobForm;
import hotel.quartz.oversea.job.base.BaseJob;
import hotel.quartz.oversea.service.JobAndTriggerService;
import hotel.quartz.oversea.util.JobRestTemplateUtils;
import hotel.quartz.oversea.vo.meituan.MtHotelVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 *  Meituan Hotel Job
 * </p>
 *
 * @description: Meituan Hotel Job
 * @author: qiuyy
 * @date: 2020-05-26 13:22
 * @version: V1.0
 */
@Slf4j
public class MeituanHotelJob implements BaseJob {
    private String jobGroupName = MeituanConstants.JOB_GROUP_NAME_MT;
    private String jobClassName = this.getClass().getName();
    private int pageSize = ApiConstans.MT_PAGE_SIZE;
    private String redisMtHotelIdsMaxId = MeituanConstants.REDIS_MT_HOTEL_IDS_MAXID;
    private int thisRedisCount = MeituanConstants.REDIS_MT_HOTEL_IDS_WAIT_COUNT;
    private String redisWaitSaveKey = MeituanConstants.REDIS_MT_HOTEL_IDS_WAIT_SAVE;
    private String timestamp = "Mhj"+String.valueOf(System.currentTimeMillis());
    @Autowired
    private JobAndTriggerService jobAndTriggerService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisService redisService;
    @Autowired
    private RestTemplate restTemplate;


    /**
     *   1、降低频繁事务操作数据库：数据缓存到redis，达到一定量 REDIS_MT_HOTEL_IDS_WAIT_COUNT，再调用saveData接口一次性保存缓存的数据
     *   2、涉及接口请求两个：a、 酒店id获取接口 b、酒店ids保存接口
     *   结果：第三方参数pageIndex页码和maxId标识，maxId为-1则无下一页数据，maxId不为-1上送查询，要有数据返回，无数据为异常，
     *
     * @param jobExecutionContext
     */
    @Override
    @SneakyThrows
    public void execute(JobExecutionContext jobExecutionContext) {
        log.info("Meituan Hotel Job {} >>>>>>>> 酒店ids定时器执行时间: {}",timestamp, DateUtil.now());
        //正式：ip+端口+网关+mapping地址?access_token//本地联调：ip+端口+mapping地址?access_token
        String host = CommonUrlConstants.MEITUAN_HOST;
        String hotelUrl = host+ UrlMtEnums.hotelIds.GET.getValue();//+"?access_token="+accessToken
        String saveUrl = host+ UrlMtEnums.hotelIds.SAVE.getValue();//+"?access_token="+accessToken
        boolean check = checkSaveRedisData(saveUrl);
        if(!check){
            //翻页标识maxId(上游接口响应参数)
            String maxId = getRedisMaxId();
            String paramsJson = "{\"maxId\":"+Integer.valueOf(maxId)+",\"pageSize\":"+ pageSize+"}";
            requestHotelIdsApi(hotelUrl,saveUrl,paramsJson);
        }
        log.info("Meituan Hotel Job {} >>>>>>>> 酒店ids定时器结束时间: {},{}",timestamp, DateUtil.now());
    }

    /**
     * 校验是否需要保存缓存的数据
     * @param saveUrl
     * @return
     */
    private boolean checkSaveRedisData(String saveUrl) {
        log.info("Meituan Hotel Job {} 酒店ids接口进入方法 checkSaveRedisData",timestamp);
        Map<String,List<Long>> getInfos = getRedisWaitData();
        if(null != getInfos && getInfos.size()>=thisRedisCount){
            //缓存酒店数据达到大于等于五thisRedisCount，则执行保存到数据库操作，可能大于thisRedisCount比如数据操作保存失败还原加入到redis
            boolean result = requestSaveWaitDataApi(saveUrl,getInfos);
            if(result){
                return true;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }

    /**
     * 调用保存缓存数据集合接口
     * @param saveRoomUrl
     * @param redisWaitDataSaveMap
     * @return
     */
    private boolean requestSaveWaitDataApi(String saveRoomUrl,Map<String,List<Long>> redisWaitDataSaveMap) {
        log.info("Meituan Hotel Job {} 酒店ids定时器进入方法 requestSaveWaitDataApi",timestamp);
        if(null == redisWaitDataSaveMap){
            //getRedisHotelRoomWaitData 有串行的可能，上次执行还没结束，这次执行，上次删除getRedisHotelRoomWaitData，这次进入提交getRedisHotelRoomWaitData，
            // 但是getRedisHotelRoomWaitData为空，所以返回false
            log.info("Meituan Hotel Job {} 酒店ids定时器进入方法 requestSaveWaitDataApi  redisWaitDataSaveMap 数据为空，不执行上送接口: {}",timestamp);
            return false;
        }
        Map map = new HashMap();
        map.put("hoteIdsListMaps",redisWaitDataSaveMap);
        //调用前,删除当前缓存的酒店数据，让下一次定时器正常查询上游接口。若调用结果失败则还原加入缓存
        deleteRedisWaitSaveData();
        //发起请求
        Result response = JobRestTemplateUtils.post(saveRoomUrl, JSON.toJSONString(map),restTemplate);
        if(response.getCode()== CommonConstants.SUCCESS){
            log.info("Meituan Hotel Job {} requestSaveWaitDataApi 保存数据成功",timestamp);
            return true;
        }else{
            log.error("Meituan Hotel Job {} requestSaveWaitDataApi 异常错误: {}",timestamp,response);
            backPutRedisWaitSaveData(redisWaitDataSaveMap);
            return false;
        }
    }
    /**
     * 删除缓存的待保存数据
     * @return
     */
    public void deleteRedisWaitSaveData(){
        log.info("Meituan Hotel Job {} 酒店ids定时器进入方法 deleteRedisWaitSaveData 删除当前酒店ids定时器数据缓存",timestamp);
        redisTemplate.delete(redisWaitSaveKey);
    }
    /**
     * 还原追加回去待保存数据
     * @return
     */
    public void backPutRedisWaitSaveData(Map<String,List<Long>> redisWaitDataMap){
        log.info("Meituan Hotel Job {} 酒店ids定时器进入方法 backPutRedisWaitSaveData 还原追加当前酒店ids缓存 ",timestamp);
        //还原redis数据
        Map<String,List<Long>> getRedisMap = (Map<String,List<Long>>)redisService.get(redisWaitSaveKey);
        if(null != getRedisMap && getRedisMap.size()>0){
            getRedisMap.putAll(redisWaitDataMap);
        }else{
            getRedisMap = redisWaitDataMap;
        }
        redisService.set(redisWaitSaveKey,getRedisMap);
    }

    /**
     * 获取保存在缓存中的待save的数据
     */
    private Map<String,List<Long>> getRedisWaitData() {
        return (Map<String,List<Long>>)redisService.get(redisWaitSaveKey);
    }
    /**
     * 请求酒店ids定时器接口
     * @param paramsJson
     */
    public void requestHotelIdsApi(String url,String saveUrl,String paramsJson){
        log.info("Meituan Hotel Job {} 酒店ids定时器进入方法 requestHotelIdsApi: {}",timestamp,paramsJson);
        Result response = JobRestTemplateUtils.post(url,paramsJson,restTemplate);
        if(response.getCode()== CommonConstants.SUCCESS && null!= response.getData()){
            //成功，解析响应结果
            MtHotelVo vo = JSON.parseObject(JSON.toJSONString(response.getData()), MtHotelVo.class);
            int maxId = vo.getMaxId();//maxId==-1时当前返回数据可能为空，且表示下一页无数据
            List<Long> hotelIds = vo.getHotelIds();
            if(null != hotelIds && hotelIds.size()!=0){
                Map<String,List<Long>> hotelIdsMap = new HashMap<>();
                hotelIdsMap.put(String.valueOf(maxId),hotelIds);
                //存入redis
                boolean setResultRedis = savePutRedisWaitData(hotelIdsMap);
                if(setResultRedis){
                    if(maxId == -1){
                        //无下一页，暂停定时器，当前执行完下次不再调用，当前立即执行查询到的数据进行保存操作
                        boolean result = requestSaveWaitDataApi(saveUrl,getRedisWaitData());
                        if(result){
                            enddingJob(true);
                        }else{
                            //log.error("Meituan Hotel Job {} 最后一页保存操作 异常错误: {},{}",timestamp,response,paramsJson);
                        }
                    }else{
                        //maxId非-1，则还存在下一页数据
                        //更新标识到redis
                        setRedisMaxid(String.valueOf(maxId));
                    }
                }else{
                    //更新失败，不更新标识，下一次定时器继续查询当前
                    log.error("Meituan Hotel Job {} 获取酒店ids setResultRedis 异常错误: {},{}",timestamp,response,paramsJson);
                }
            }else{
                //enddingJob(false);
                if(maxId!=-1){
                    log.error("Meituan Hotel Job {} 获取酒店ids定时器异常错误 hotelIds null: {},{}",timestamp,response,paramsJson);
                }else{
                    //无下一页，暂停定时器，当前执行完下次不再调用，当前立即执行查询到的数据进行保存操作
                    boolean result = requestSaveWaitDataApi(saveUrl,getRedisWaitData());
                    if(result){
                        enddingJob(true);
                    }else{
                        log.error("Meituan Hotel Job {} 最后一页保存操作 异常错误: {},{}",timestamp,response,paramsJson);
                    }
                }
            }
        }else{
            //enddingJob(false);
            log.error("Meituan Hotel Job {} 获取酒店ids定时器异常错误: {},{}",timestamp,response,paramsJson);
        }
    }
    /**
     * 成功请求酒店房型接口后，合法酒店房型数据，追加进缓存中
     */
    private boolean savePutRedisWaitData(Map<String, List<Long>> mapRealRoomVoMap) {
        Map<String,List<Long>> getRedisMap = (Map<String,List<Long>>)redisService.get(redisWaitSaveKey);
        if(null != getRedisMap && getRedisMap.size()>0){
            getRedisMap.putAll(mapRealRoomVoMap);
        }else{
            getRedisMap = mapRealRoomVoMap;
        }
        boolean result = redisService.set(redisWaitSaveKey,getRedisMap);
        return result;
    }
    private String getRedisMaxId() {
        String maxId = (String)redisTemplate.opsForValue().get(redisMtHotelIdsMaxId);
        if(null==maxId){
            //首次请求数据
            maxId = "0";
            redisTemplate.opsForValue().set(redisMtHotelIdsMaxId,maxId);
        }
        return maxId;
    }
    public void setRedisMaxid(String newMaxid){
        redisTemplate.opsForValue().set(redisMtHotelIdsMaxId,newMaxid);
    }
    public void deleteRedisMaxid(){
        redisTemplate.delete(redisMtHotelIdsMaxId);
    }
    public void enddingJob(boolean delKey){
        JobForm jobForm = new JobForm();
        jobForm.setJobClassName(jobClassName);
        jobForm.setJobGroupName(jobGroupName);
        try {
            jobAndTriggerService.pauseJob(jobForm);
            log.warn("Meituan Hotel Job {} 执行暂停job: {},{}",timestamp,jobForm);
            if(delKey){
                deleteRedisMaxid();
                redisTemplate.delete(redisWaitSaveKey);
                redisTemplate.delete(thisRedisCount);
            }
        } catch (SchedulerException e) {
            log.error("Meituan Hotel Job {} 执行暂停job异常: {}",timestamp,jobForm);
            log.error(e.getMessage(),e);
        }
    }
}
